/**
 * 
 */
package truerefactor.refactor;

import java.util.List;

import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;

/**
 * @author Isaac
 */
public class InlineClass extends Refactoring {

    /**
     * 
     */
    public InlineClass()
    {
        this.name = "Inline Class";
        this.description = "";
    }
    
    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeNode
     * [])
     */
    @Override
    public void execute(CodeGraph graph)
    {
        ClassNode source = (ClassNode) operands.get(0);
        ClassNode target = (ClassNode) operands.get(1);
        
        if (source.getSubClasses().size() > 0)
            return;
        
        // 1. Declare the public protocol of the source class onto the absorbing
        // class. Delegate all these methods to the source class.
        // a. If a separate interface makes sense for the source class methods,
        // use Extract Interface before inlining
        // This step is handled below

        adjustReferences(graph, source, target);

        moveFeatures(graph, source, target);

        removeSourceClass(graph, source);
    }

    private void removeSourceClass(CodeGraph graph, ClassNode source)
    {
        for (ClassNode cNode : graph.getClasses()) 
        {
            Edge edge = null;
            if (graph.isEdge(cNode, source, EdgeType.Association))
                edge = graph.getEdge(cNode, source, EdgeType.Association);
            else if (graph.isEdge(cNode, source, EdgeType.Composition))
                edge = graph.getEdge(cNode, source, EdgeType.Composition);
            else
                edge = graph.getEdge(cNode, source, EdgeType.Aggregation);
            
            cNode.removeAssociatedClass(source);
//            graph.remove(edge);
            
            if (graph.isEdge(source, cNode, EdgeType.Association))
                edge = graph.getEdge(source, cNode, EdgeType.Association);
            else if (graph.isEdge(source, cNode, EdgeType.Composition))
                edge = graph.getEdge(source, cNode, EdgeType.Composition);
            else
                edge = graph.getEdge(source, cNode, EdgeType.Aggregation);
            
            source.removeAssociatedClass(cNode);
//            graph.remove(edge);
        }
        
        graph.remove(source);
    }

    private void moveFeatures(CodeGraph graph, ClassNode source, ClassNode target)
    {
        // 3. Use Move Method and Move Field to move features from the source
        // class to the absorbing class until nothing is left.
        List<CodeNode> srcMethods = source.getMethods();
        for (int i = 0; i < srcMethods.size(); i++)
        {
            CodeNode node = srcMethods.get(i);
            
            Refactoring mover = RefactoringFactory.getInstance().createMoveMethod(node, target);
            mover.execute(graph);
        }

        List<CodeNode> srcFields = source.getAttributes();
        for (int i = 0; i < srcFields.size(); i++)
        {
            CodeNode node = srcFields.get(i);

            Refactoring mover = RefactoringFactory.getInstance().createMoveField(node, target);
            mover.execute(graph);
        }
    }

    private void adjustReferences(CodeGraph graph, ClassNode source, ClassNode target)
    {
        // 2. Change all references from the source class to the absorbing class
        // a. Declare the same class private to remove out-of-package
        // references. Also change the name of the source class so the compiler
        // catches any dangling references to the source class
        List<ClassNode> classes = graph.getClasses();
        for (ClassNode cNode : classes)
        {
            if (graph.isEdge(cNode, source, EdgeType.Association) || graph.isEdge(cNode, source, EdgeType.Composition) || graph.isEdge(cNode, source, EdgeType.Aggregation))
            {
                List<AttributeNode> attrs = cNode.getAttributeByType(source.getIdentifier());
                for (AttributeNode attr : attrs)
                {
                    attr.setType(target.getIdentifier());
                    attr.setAssociatedClass(target);
                }
                
                Edge edge = null;
                if (graph.isEdge(cNode, source, EdgeType.Association))
                    edge = graph.getEdge(cNode, source, EdgeType.Association);
                else if (graph.isEdge(cNode, source, EdgeType.Composition))
                    edge = graph.getEdge(cNode, source, EdgeType.Composition);
                else
                    edge = graph.getEdge(cNode, source, EdgeType.Aggregation);
                
                cNode.removeAssociatedClass(source);
                graph.remove(edge);
                graph.insert(new Edge(cNode, target, edge.getType()));
                cNode.addAssociatedClass(target);
            }
        }
    }
}
